/** @file         led_pdrv.c
 *  @brief        led 平台驱动文件
 *  @details      基于驱动模块的方式实现 驱动注册/注销等等操作。
 *  @author       lzm
 *  @date         2021-03-30 20:22:03
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 *  2020-04-01:
 *      基于设备树方式获取硬件资源。
 **********************************************************
*/

#include "led_pdrv.h"

/* 
 * 步骤注释声明
 * [module]：模块框架
 * [bus_dev]：总线设备框架。
 * [bus_drv]：总线驱动框架。
 * [drv]：实现驱动内容。包括内核设备文件、设备节点等等。
 * 总线设备框架和总线驱动框架都是属于内核的架子，和模块框架一个性质。 
 */

/* [drv] 驱动相关变量 */
static dev_t led_dev_num_start = 0; // 占用设备号
static struct class *led_pdrv_class; // 设备类

/* [dev_tree] 设备树相关变量 */
struct device_node *lss_led_dev_node; // led 设备树节点



/* [drv][1] 驱动操作内容实现 */
/** @brief  led_dev_open
  * @param 
  * @retval 
  * @author lzm
  */
static int led_dev_open(struct inode *inode, struct file *filp)
{
    unsigned int val;
    led_data_t *cur_led = container_of(inode->i_cdev, led_data_t, led_cdev);

    printk("%d-%s\n", __LINE__, __func__);

    /* 引脚配置 */
    /* 初始化时钟 */
    val = ioread32(cur_led->va_ccm_ccgrx);
    val &= ~(3 << cur_led->clock_offset);
    val |= (3 << cur_led->clock_offset);
    iowrite32(val, cur_led->va_ccm_ccgrx);
    /* 端口复用 */
    iowrite32(0x05, cur_led->va_iomuxc_mux);
    /* 电气属性 */
    iowrite32(0x0001F838, cur_led->va_iomux_pad);
    /* 输出模式 */
    val = ioread32(cur_led->va_gdir);
    val &= ~(1 << cur_led->pin);
    val |= (1 << cur_led->pin);
    iowrite32(val, cur_led->va_gdir);
    /* 输出高电平 */
    val = ioread32(cur_led->va_dr);
    val |= (0x01 << cur_led->pin);
    iowrite32(val, cur_led->va_dr);
    
    cur_led->status = 1;

    /* 保存该结构体地址到 设备文件中 */
    filp->private_data = cur_led; // 它操作文件函数也可以调用该私人设备结构体了

    return 0;
}

/** @brief  led_dev_release
  * @param 
  * @retval 
  * @author lzm
  */
static int led_dev_release(struct inode *inode, struct file *filp)
{
    printk("%d-%s\n", __LINE__, __func__);

    return 0;
}

/** @brief  led_dev_write
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t led_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long ret;
    unsigned int val;
    int tmp = count;
    led_data_t *cur_led = (led_data_t *)filp->private_data;

    printk("%d-%s\n", __LINE__, __func__);

    /* 获取来自用户的控制数据 */
    kstrtoul_from_user(buf, tmp, 10, &ret);

    /* 对 LED 进行控制 */
    val = ioread32(cur_led->va_dr);
    if(ret)
    {
        val |= (0x01 << cur_led->pin);
        cur_led->status = 1;
    }
    else
    {
        val &= ~(0x01 << cur_led->pin);
        cur_led->status = 0;
    }
    iowrite32(val, cur_led->va_dr);

    *ppos += tmp; // 一直记着

    return tmp;
}

/** @brief  led_dev_read
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t led_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned char led_status;
    int ret;
    led_data_t *cur_led = (led_data_t *)(filp->private_data);

    printk("%d-%s\n", __LINE__, __func__);

    led_status = cur_led->status;
    ret = copy_to_user(buf, &led_status, 1);

    return 1;
}


/* [drv][2] 驱动操作结构体填充 */
static struct file_operations led_dev_fops =
{
    .owner = THIS_MODULE,
    .open = led_dev_open,
    .release = led_dev_release,
    .write = led_dev_write,
    .read = led_dev_read,
};

/* [bus_drv][1] 总线驱动内容实现 */
/** @brief  led_pdrv_probe 匹配成功后的回调函数
  * @param  
  * @retval
  * @author lzm
  */
static int led_pdrv_probe(struct platform_device *pdev)
{
    unsigned int ret;
    dev_t cur_dev_num; // 当前设备设备号
    led_data_t *cur_led; // 当前设备，私人结构体
    unsigned int *pin_hw_info; // 偏移数据
    unsigned int cur_dev_node_id = 0; // 设备树中定义的 id

    printk("%d-%s\n", __LINE__, __func__);

    /* [bus_drv]** 先申请内存固定该地址 */
    cur_led = devm_kzalloc(&pdev->dev, sizeof(led_data_t), GFP_KERNEL);
    if(!cur_led)
    {
        printk("error for [cur_led] [devm_kzalloc]!\n");
        return -ENOMEM;
    }
    pin_hw_info = kzalloc(sizeof(unsigned int)*2, GFP_KERNEL);
    if(!pin_hw_info)
    {
        printk("error for [pin_hw_info] [devm_kzalloc]!\n");
        return -ENOMEM;
    }

    /* [dev_tree][1] 获取设备树节点 */
    lss_led_dev_node = of_find_node_by_path("/lss_led");
    if(lss_led_dev_node == NULL)
    {
        printk(KERN_ERR "\n  get [lss_led] node faild!\n");
        return -1;
    }

    /* 获取具体设备的设备树节点 */
    cur_led->dev_node = of_find_node_by_path("/lss_led/lss_led_red");
    if(lss_led_dev_node == NULL)
    {
        printk(KERN_ERR "\n  get [lss_led_red] node faild!\n");
        return -1;
    }

    /* [dev_tree][2] 获取节点属性 */
    cur_led->va_ccm_ccgrx = of_iomap(cur_led->dev_node, 0);
    cur_led->va_iomuxc_mux = of_iomap(cur_led->dev_node, 1);
    cur_led->va_iomux_pad = of_iomap(cur_led->dev_node, 2);
    cur_led->va_gdir = of_iomap(cur_led->dev_node, 3);
    cur_led->va_dr = of_iomap(cur_led->dev_node, 4);

    /* 获取自定义属性值 */
    ret = of_property_read_u32(cur_led->dev_node, "id", &cur_dev_node_id);
    if(ret != 0)
    {
        printk("\n get device tree node [id] fail!\n");
        return ret;
    }
    ret = of_property_read_u32_array(cur_led->dev_node, "pin_hw_info", pin_hw_info, 2);
    if(ret != 0)
    {
        printk("\n get device tree node [pin_hw_info] fail!\n");
        return ret;
    }

    /* 继续填充 led 结构体 */
    cur_led->pin = pin_hw_info[0];
    cur_led->clock_offset = pin_hw_info[1];

    /* 以上已经获取完所需资源 */

    /* 编写驱动 */
    /* [drv][3][1] 申请设备号 */
    cur_dev_num = MKDEV(MAJOR(led_dev_num_start), cur_dev_node_id);
    register_chrdev_region(cur_dev_num, 1, LED_DEV_NAME);
    /* [drv][5] 初始化设备内核文件。绑定驱动操作文件 */
    cdev_init(&cur_led->led_cdev, &led_dev_fops);
    /* [drv][6] 注册设备内核文件。绑定设备号 */
    cdev_add(&cur_led->led_cdev, cur_dev_num, 1);
    /* [drv][7] 创建设备节点。绑定设备号&设备类 */
    device_create(led_pdrv_class, NULL, cur_dev_num, NULL, LED_DEV_INODE_NAME"%d", cur_dev_node_id);

    /* 继续填充 led 结构体 */
    cur_led->dev_num = cur_dev_num;

    /* [bus_drv][1][1] 把 led 数据传入平台设备结构体中 */
    platform_set_drvdata(pdev, cur_led); // pdev->dev->driver_data = cur_led

    /* 先放掉不需要的内存 */
    kfree(pin_hw_info);

    return 0;

}


/** @brief  led_pdrv_remove 对应的设备被注销或者本驱动被注销时调用该函数
  * @param  
  * @retval
  * @author lzm
  */
int led_pdrv_remove(struct platform_device *pdev)
{
    dev_t cur_dev_num;
    led_data_t *cur_led = platform_get_drvdata(pdev);

    printk("%d-%s\n", __LINE__, __func__);

    /* 释放内存 */
    iounmap(cur_led->va_ccm_ccgrx);
    iounmap(cur_led->va_iomuxc_mux);
    iounmap(cur_led->va_iomux_pad);
    iounmap(cur_led->va_gdir);
    iounmap(cur_led->va_dr);


    /* 获取当前设备设备号 */
    cur_led= cur_led->dev_num;

    /* [drv][8] 删除设备节点 */
    device_destroy(led_pdrv_class, cur_dev_num);
    /* [drv][9] 删除设备内核文件 */
    cdev_del(&cur_led->led_cdev);
    /* [drv][10] 归还设备号 */
    unregister_chrdev_region(cur_dev_num, 1);

    return 0;
}


/* 设备树匹配表 */
static const struct of_device_id lss_led_ids[] =
{
    {.compatible = "lzm,lss_led"}, // 兼容 leds。注意对应哪个设备树节点。
    {}
};

/* [bus_drv][2] 总线驱动结构体填充 */
struct platform_driver led_pdrv =
{
    .probe = led_pdrv_probe,
    .driver =
    {
        .owner = THIS_MODULE,
        .name = "led_pdev", // 可用于匹配设备
        .of_match_table = lss_led_ids, // 设备树匹配表
    }
};

/* [bus_drv][3] 总线驱动属性文件资源 */


/* [module][1] 模块入口函数 */
/** @brief  入口函数
  * @param  
  * @retval 
  * @author lzm
  */
static __init int led_pdrv_init(void)
{
    printk("%d-%s\n", __LINE__, __func__);

    /* [drv][3] 申请设备号，用于占用主设备号 */
    alloc_chrdev_region(&led_dev_num_start, 0, 1, LED_DEV_NAME);

    /* [drv][4] 创建设备类 */
    led_pdrv_class = class_create(THIS_MODULE, LED_DEV_CLASS);

    /* [bus_drv][4] 注册平台驱动 */
    platform_driver_register(&led_pdrv);

    /* [bus_drv][5] 创建平台驱动属性文件 */

}
module_init(led_pdrv_init);

/* [module][2] 模块出口函数 */
/** @brief  出口函数
  * @param  
  * @retval 
  * @author lzm
  */
static __exit void led_pdrv_exit(void)
{
    printk("%d-%s\n", __LINE__, __func__);

    /* [bus_drv][6] 删除平台驱动属性文件 */

    /* [bus_drv][7] 注销平台驱动 */
    platform_driver_unregister(&led_pdrv);

    /* [drv][7] 归还占用设备号 */
    unregister_chrdev_region(led_dev_num_start, 1);

    /* [drv][8] 删除设备类 */
    class_destroy(led_pdrv_class);

}
module_exit(led_pdrv_exit);

/* [module][3] 模块协议 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lizhuming");
MODULE_DESCRIPTION("this is a example for led platform driver base on device tree");


